package org.stars.daostars;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.dao.DaoBaseNew;
import org.stars.dao.exception.DaoException;
import org.stars.daostars.DaoBase.PrintDebugType;
import org.stars.daostars.core.DaoMap;
import org.stars.daostars.core.SqlParameter;
import org.stars.daostars.core.SqlQuery;
import org.stars.daostars.core.parser.SqlParser;
import org.stars.daostars.core.runtime.DaoQueryRunner;
import org.stars.daostars.core.runtime.DaoRuntime;
import org.stars.daostars.core.runtime.DaoRuntimeHelper;
import org.stars.daostars.core.runtime.EntityBeanRsHandler;
import org.stars.daostars.core.runtime.SimpleQuery;
import org.stars.daostars.sqlmapper.SqlMapper;
import org.stars.database.ConnectionPoolManager;
import org.stars.util.StringUtility;

public abstract class SelectRuntime {

	/**
	 * logger del dao.
	 * 
	 * @return logger
	 */
	public static Log getLog() {
		return LogFactory.getLog(SelectRuntime.class);
	}
	
	/**
	 * Esegue una query di tipo select. L'utente che vuole eseguire la query
	 * viene preso dall'attributo <code>user</code> del Dao.
	 * @param <T>
	 * 
	 * @param queryName
	 *            nome della query
	 * @param moreSql
	 *            codice aggiuntivo da accodare alla query
	 * @param dto
	 *            entity bean contenente i parametri della query
	 * @param indexedParameters
	 *            array di parametri della query
	 * @param classResultType
	 *            tipo di entity bean restituito dalla query
	 * @return numero di elementi letti dalla query
	 * 
	 * @throws Exception
	 *             in caso di errore
	 */
	public static <T> SelectResult<T> select(DaoBaseNew<?> dao, QueryConfig config, Connection connection, String queryName, String moreSql, Object dto, Object[] indexedParameters, Class<T> classResultType) throws Exception {
		Log log = getLog();
		// Object resultType = null;
		SimpleQuery ret = null;

		if (dto == null) {
			// se non esiste lo creiamo del tipo che
			// specializza il dao
			dto = dao.entityBeanClass.newInstance(); 
		}

		if (indexedParameters == null)
			indexedParameters = new Object[0];
		
		if (classResultType==null)
		{
			DaoException ex = new DaoException("In the dao " + dao.sqlMapper.getName() + " result type is null!");
			log.error(ex);
			throw (ex);	
		}

		// prendiamo da eb i nomi degli attributi in esso definiti
		if (dao.sqlMapper == null) {
			DaoException ex = new DaoException("The definition of the dao " + dao.sqlMapper.getName() + " does not exist!");
			log.error(ex);
			throw (ex);
		}
		SqlQuery query = dao.sqlMapper.getQuery(queryName);
		if (query == null) {
			String stackTrace=DaoBaseNewHelper.getStackTrace(config);
			DaoException ex = new DaoException("In the dao definition " + dao.sqlMapper.getName() + " the query " + queryName + " does not exist!");
			log.error(ex);
			throw(ex);
		}

		SqlQuery moreSqlQuery = null;

		moreSql = StringUtility.nvl(moreSql);
		if (moreSql.length() > 0) {
			moreSqlQuery = SqlParser.parseQuery(moreSql);
		}

		SqlQuery orderSqlQuery = null;
		// prendiamo con priorità l'order da codice o l'order in xml
		String orderByName = query.getOrderByName().trim().toUpperCase();

		if (orderByName.length() != 0) {
			orderSqlQuery = dao.sqlMapper.getQuery(orderByName);

			if (orderSqlQuery == null) {
				DaoException ex = new DaoException("In the dao definition " + dao.sqlMapper.getName() + " the query for order " + orderByName + " does not exist!");
				log.error(ex);
				throw (ex);
			}
		}
		
		Map<String, String> variables = new HashMap<String, String>();
		variables=dao.sqlMapper.getVariableSetCopy();
		variables.putAll(config.getVariables());

		if (!query.isPreconditionRespected(config.getUser(), dto, indexedParameters, variables)) {
			DaoException ex = new DaoException("In the dao definition " + dao.sqlMapper.getName() + " the precondition " + query.getPrecondition() + " for the query " + query.getName()
					+ " is not satisfeid!");
			log.error(ex);
			throw (ex);
		}

		ret = DaoRuntime.getSimpleQuery(config.getUser(), dto, indexedParameters, variables, query, moreSqlQuery, orderSqlQuery);

		return readSQL(queryName, ret.preparedSql, ret.preparedParameters, ret.preparedParameterNames, classResultType);
	}
	
	
	/**
	 * E' il metodo alla base di tutte le operazioni di tipo SELECT. Il codice
	 * sql che arriva a questo metodo è già stato elaborato, quindi tutti i
	 * parametri sono stati analizzati e sostituiti con il ? (nel caso di db
	 * oracle).
	 * <p>
	 * Anche tutte gli statement condizionali sono stati analizzati e rimossi o
	 * valorizzati a seconda del fatto che le precondizioni fossero soddisfatte
	 * o meno.
	 * 
	 * @param sqlName
	 *            indica il nome della query che si sta eseguendo. Serve per i
	 *            mesasggi di diagnostica. Può essere anche messo a
	 *            <code>null</code>.
	 * @param sql
	 *            stringa sql da eseguire.
	 * @param whereConditions
	 *            clausole di where da aggiungiungere al codice sql
	 * @param parms
	 *            valori dei parametri da definire per l'intera query.
	 * 
	 * @return restituisce il numero di elementi recuperati.
	 * @throws Exception
	 *             any exception
	 */
	@SuppressWarnings("unchecked")
	protected static <T> SelectResult<T> readSQL(SqlMapper sqlMapper, Connection connection, String sqlName, String sql, Object[] parms, List<SqlParameter> paramName, Class<T> resultType) throws Exception {
		SelectResult<T> result=new SelectResult<T>();
		Log log = getLog();

		// facciamo un controllo, se sql non contiene un codice valido, allora
		// usciamo senza far nulla
		sql = StringUtility.nvl(sql).trim();
		if (sql.length() == 0) {
			// "ATTENZIONE: Si e' cercato di recuperare una codice SQL vuoto.
			// Forse il nome della query none' corretto?");
			String sMsg = "WARNING Dao: " + sqlMapper.getName() + " Query: " + StringUtility.nvl(sqlName, "<Unknown>")
					+ " The sql string is empty. Perhaps the name of the query is wrong?";
			printDebugSQL(sMsg, PrintDebugType.WARN);

			elencoBean = new ArrayList<T>();
			createResultBean();

			return 0;
		}

		int res = RETURN_ERROR;
		// ResultSetHandler rsHandler = new
		// EntityBeanRsHandler(dettaglioBean.getClass());

		EntityBeanRsHandler<T> rsHandler = new EntityBeanRsHandler<T>(resultType);

		// impostiamo il gestore dell'evento ricezione del bean
		rsHandler.setOnGetEntityEventHandler(eventHandler);
		rsHandler.setDaoName(daoClassName);
		
		if (StringUtility.nvl(sqlName).length()>0)
		{
			SqlQuery query=sqlMapper.getQuery(sqlName);
			
			String conversionMapName=StringUtility.nvl(query.getConversionMapName());
			
			if (conversionMapName.length()>0)
			{
				DaoMap map=sqlMapper.getMap(conversionMapName);
				rsHandler.setConversionMap(map);
			}
		}
		
		boolean standAlone;

		try {
			standAlone=false;
			if (connection==null)
			{
				standAlone=true;
				connection=ConnectionPoolManager.getInstance().getConnection(sqlMapper.getDefaultConnectionName());
			}

			// processiamo la lingua
			String sSql = processLanguage(sql);

			printDebugStack();

			printConnectionInfo(conn, transaction!=null);
			printDebugSQL("Dao: " + daoClassName + " Query: " + StringUtility.nvl(sqlName, "<Unknown>") + " " + " read SQL: " + sSql, parms, paramName);

			// puliamo l'elenco delle colonne recuperate
			columns = null;
			List<E> l = (List<E>) DaoQueryRunner.getInstance().query(conn, sSql, rsHandler, parms);			

			// memorizza l'array delle colonne recuperate
			columns = rsHandler.getColumns();

			printDebugSQL("# row read: " + l.size());
			printDebugSQL("Time for execute sql: " + rsHandler.getExecutionTimeOfSql() + " ms, Time for populate the eb list: " + rsHandler.getExecutionTimeOfRetriveList()
					+ " ms, Time total: " + rsHandler.getTotalExecutionTime() + " ms");

			if (l != null) {
				res = l.size();
				this.elencoBean = l;
				iteratorElencoBean = elencoBean.iterator();

				if (res > 0) {
					// ASSERT: lo imposto solo se trovo qualcosa
					dettaglioBean = l.get(0);
				} else {
					// altrimenti creiamo un nuovo dettaglio bean
					// dettaglioBean=resultType.newInstance();
					createResultBean();
				}
			} else {
				// Nel caso non c'e' niente creiamo una lista vuota
				elencoBean = new LinkedList();
				iteratorElencoBean = elencoBean.iterator();
				// il dettaglio bean deve essere del tipo desiderato
				// dettaglioBean = resultType.newInstance();
				createResultBean();
			}
		} catch (SQLException e) {
			log.fatal(e.getMessage());
			throw (exceptionTranslator.translate(sql, e, DaoRuntimeHelper.getDatabaseInformation(connection)));
		} catch (Exception e) {
			log.fatal(e.getMessage());
			throw (new DaoException(e.getMessage(), e));
		} finally {
			if (standAlone) {
				DbUtils.closeQuietly(connection);
			}
		}
		return res;
	}


}
